using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using ProjectEuler.Core.Helpers;

namespace ProjectEuler.Core
{
    public class Problem60 : IProjectEulerProblem
    {
        public int Number
        {
            get { return 60; }
        }

        public string Description
        {
            get { return "Find the lowest sum for a set of five primes for which any two primes concatenate to produce another prime"; }
        }

        public string Answer
        {
            get { return GetLowestRemarkableSet().Sum().ToString(); }
        }

        // shamelessly ripped off of: http://blog.mycila.com/2009/08/project-euler-problem-60.html
        protected IList<int> GetLowestRemarkableSet()
        {
            var primes = GetPrimes(10000).ToArray();
            for (int p1 = 0, p1max = primes.Length - 5; p1 < p1max; p1++)
            {
                for (int p2 = p1 + 1, p2max = p1max + 1; p2 < p2max; p2++)
                {
                    if (!IsRemarkableSet(new[] { primes[p1], primes[p2] })) continue;
                    for (int p3 = p2 + 1, p3max = p2max + 1; p3 < p3max; p3++)
                    {
                        if (!IsRemarkableSet(new[] { primes[p1], primes[p2], primes[p3] })) continue;
                        for (int p4 = p3 + 1, p4max = p3max + 1; p4 < p4max; p4++)
                        {
                            if (!IsRemarkableSet(new[] { primes[p1], primes[p2], primes[p3], primes[p4] })) continue;
                            for (int p5 = p4 + 1, p5max = p4max + 1; p5 < p5max; p5++)
                            {
                                if (!IsRemarkableSet(new[] { primes[p1], primes[p2], primes[p3], primes[p4], primes[p5] })) continue;
                                return new[] {primes[p1], primes[p2], primes[p3], primes[p4], primes[p5]};
                            }
                        }
                    }
                }
            }
            throw new Exception("this exception should never be reached");
        }

        protected bool IsRemarkableSet(IEnumerable<int> set)
        {
            var testSet = set.ToArray();

            return (from a in testSet
                    from b in testSet
                    where a != b
                    select new {cat1 = int.Parse(a + "" + b), cat2 = int.Parse(b + "" + a)})
                .All(t => MathHelper.IsPrime(t.cat1) && MathHelper.IsPrime(t.cat2));
        }

        private static IEnumerable<int> GetPrimes(int max)
        {
            yield return 2;
            for (int i = 3; i < max; i++)
            {
                if (MathHelper.IsPrime(i)) yield return i;
            }
        }
    }

    [TestFixture]
    public class Problem60tests : Problem60
    {
        [Test]
        public void Can_test_if_its_a_remarkable_set()
        {
            Assert.AreEqual(true, IsRemarkableSet(new[] {3, 7}));
            Assert.AreEqual(true, IsRemarkableSet(new[] {3, 7, 109}));
            Assert.AreEqual(true, IsRemarkableSet(new[] {3, 7, 109, 673}));
        }
    }
}